/* 
* linux-3.0.8/drivers/input/keyboard/gt2440_key.c
*/

#include <linux/module.h>

#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/workqueue.h>
#include <linux/gpio.h>

#include <mach/regs-gpio.h>
#include "gt2440_key.h"




/***** data structure *****/

/***** help function *****/
static void gt2440_key_report_event(struct gt2440_key_data *data)
{
	struct gt2440_key_button *button = data->button;
	struct input_dev *input = data->input;
	unsigned int type = button->type ?: EV_KEY;
	int state = (gpio_get_value_cansleep(button->gpio) ? 1 : 0) ^ button->active_low;
	if (type == EV_ABS) {
		if (state)
			input_event(input, type, button->code, button->value);
	} else {
		input_event(input, type, button->code, !!state);
	}
	input_sync(input);
}



/***** input dev ops function *****/
static int gt2440_key_open(struct input_dev *input)
{
	struct gt2440_key_info *info = input_get_drvdata(input);
	return info->enable ? info->enable(input->dev.parent) : 0;
}
static void gt2440_key_close(struct input_dev *input)
{
	struct gt2440_key_info *info = input_get_drvdata(input);
	if (info->disable)
		info->disable(input->dev.parent);
}

/***** timer and work function *****/
static void gt2440_key_timer(unsigned long _data)
{
	struct gt2440_key_data *data = (struct gt2440_key_data *)_data;
	schedule_work(&data->work);
}
static void gt2440_key_work(struct work_struct *work)
{
	struct gt2440_key_data *data =
		container_of(work, struct gt2440_key_data, work);
	gt2440_key_report_event(data);
}



/***** interrupt function *****/
static irqreturn_t gt2440_key_irq(int irq, void *dev_id)
{
	struct gt2440_key_data *data = dev_id;
	struct gt2440_key_button *button = data->button;

	BUG_ON(irq != gpio_to_irq(button->gpio));

	if (data->timer_debounce)
		mod_timer(&data->timer,
			jiffies + msecs_to_jiffies(data->timer_debounce));
	else
		schedule_work(&data->work);
	return IRQ_HANDLED;
}



/***** gt2440 board probe and remove function *****/
static int gt2440_key_setup(struct gt2440_key_info *info,
					 struct gt2440_key_data *data,
					 struct gt2440_key_button *button)
{
	const char *desc = button->desc ? button->desc : "gpio_keys";
	unsigned long irqflags;
	int ret;

	setup_timer(&data->timer, gt2440_key_timer, (unsigned long)data);
	INIT_WORK(&data->work, gt2440_key_work);

	ret = gpio_request(button->gpio, desc);
	if (ret < 0) {
		dev_err(info->dev, "failed to request GPIO %d, error %d\n", button->gpio, ret);
		return ret;
	}
	data->gpio = button->gpio;
	//ret = gpio_direction_input(button->gpio);
	ret = s3c_gpio_cfgpin(data->gpio, S3C2410_GPIO_IRQ);
	if (ret < 0) {
		dev_err(info->dev, "failed to configure GPIO %d, error %d\n",button->gpio, ret);
		goto exit_1;
	}
	if (button->debounce_interval) {
		ret = gpio_set_debounce(button->gpio,
					  button->debounce_interval * 1000);
		if (ret < 0)
			data->timer_debounce = button->debounce_interval;
	}
	data->irq = button->irq;
	if (data->irq < 0) {
		dev_err(info->dev, "failed to get irq GPIO %d, error %d\n",button->gpio, ret);
		goto exit_1;
	}
	irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
	if (!button->can_disable)
		irqflags |= IRQF_SHARED;

	ret = request_any_context_irq(data->irq, gt2440_key_irq, irqflags, desc, data);
	if (ret < 0) {
		dev_err(info->dev, "failed to request irq %d, error %d\n",data->irq, ret);
		goto exit_1;
	}

	return 0;
exit_1:	
	gpio_free(button->gpio);
	return ret;
}

static int gt2440_key_probe(struct platform_device *pdev)
{
	struct gt2440_key_platdata *pdata ;
	struct gt2440_key_info *info;
	struct input_dev *input;
	int i, ret;
	int wakeup = 0;

	pdata = pdev->dev.platform_data;
	if (pdata == NULL) {
		dev_err(&pdev->dev,"failed to get platform data for key\n");
		return -EINVAL;
	}
	info = kzalloc(sizeof(struct gt2440_key_info) +
			pdata->nbuttons * sizeof(struct gt2440_key_data),
			GFP_KERNEL);
	if(info == NULL){
		dev_err(&pdev->dev,"failed to allocate struct gt2440_key_info{} for key\n");
		return -ENOMEM;
	}
	info->dev = &pdev->dev;
	info->nbuttons = pdata->nbuttons;
	info->enable = pdata->enable;
	info->disable = pdata->disable;
	mutex_init(&info->disable_lock);
	platform_set_drvdata(pdev, info);
	
	input = input_allocate_device();
	if (input == NULL){
		dev_err(&pdev->dev,"failed to allocate struct input_dev{} for ts \n");
		ret = -ENOMEM;
		goto	exit_1;
	}
	info->input = input;
	input->name = pdata->name ? : pdev->name;
	input->phys = "gt2440-key/input0";
	input->id.bustype = BUS_HOST;
	input->id.vendor = 0x0001;
	input->id.product = 0x0001;
	input->id.version = 0x0100;
	input->open = gt2440_key_open;
	input->close = gt2440_key_close;
	input->dev.parent = &pdev->dev;
	input_set_drvdata(input, info);
	
	if (pdata->rep)
		__set_bit(EV_REP, input->evbit);

	for (i = 0; i < pdata->nbuttons; i++) {
		struct gt2440_key_button *button = &pdata->buttons[i];
		struct gt2440_key_data *data = &info->data[i];
		unsigned int type = button->type ?: EV_KEY;
		data->input = input;
		data->button = button;
		ret = gt2440_key_setup(info, data, button);
		if (ret){
			
			goto exit_2;
		}
		if (button->wakeup)
			wakeup = 1;
		input_set_capability(input, type, button->code);
	}
	/*
	ret = sysfs_create_group(&pdev->dev.kobj, &gt2440_key_attr_group);
	if (ret) {
		dev_err(&pdev->dev, "failed to export keys/switches, error: %d\n",ret);
		goto exit_2;
	}
	*/
	ret = input_register_device(input);
	if (ret) {
		dev_err(&pdev->dev, "failed to register input device, error: %d\n",ret);
		goto exit_3;
	}

	for (i = 0; i < pdata->nbuttons; i++)
		gt2440_key_report_event(&info->data[i]);
	input_sync(input);
	device_init_wakeup(&pdev->dev, wakeup);
	return 0;

 exit_3:
	//sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
 exit_2:
	while (--i >= 0) {
		free_irq(info->data[i].irq, &info->data[i]);
		if (info->data[i].timer_debounce)
			del_timer_sync(&info->data[i].timer);
		cancel_work_sync(&info->data[i].work);
		gpio_free(info->data[i].gpio);
	}
	input_free_device(input);
	info->input = NULL;
exit_1:
	kfree(info);
	platform_set_drvdata(pdev, NULL);
	return ret;
}

static int gt2440_key_remove(struct platform_device *pdev)
{
	struct gt2440_key_info *info = platform_get_drvdata(pdev);
	int i;
	
	if(info == NULL)
		return 0;

	if(info->input != NULL){
		input_unregister_device(info->input);
		info->input = NULL;
	}
	device_init_wakeup(&pdev->dev, 0);
	//sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);

	for (i = 0; i < info->nbuttons; i++) {
		free_irq(info->data[i].irq, &info->data[i]);
		if (info->data[i].timer_debounce)
			del_timer_sync(&info->data[i].timer);
		cancel_work_sync(&info->data[i].work);
		gpio_free(info->data[i].gpio);
	}
	kfree(info);
	platform_set_drvdata(pdev, NULL);
	return 0;
}


/***** gt2440 board probe and remove function *****/
static int gt2440_board_key_probe(struct platform_device *pdev)
{	dprintk("gt2440_board_key_probe(id=%d)\n", pdev->id);

	return gt2440_key_probe(pdev);
}
static int gt2440_board_key_remove(struct platform_device *pdev)
{	dprintk("gt2440_board_key_remove(id=%d)\n", pdev->id);

	return gt2440_key_remove(pdev);
}

static struct platform_driver gt2440_key_platform_driver = {
	.probe		= gt2440_board_key_probe,
	.remove		= gt2440_board_key_remove,
	.driver		= {
		.name	= PLAT_DEVICE_NAME, /* "gt2440-rtc" */
		.owner	= THIS_MODULE,
	},
};


/***** init and exit ****/
static int __init gt2440_key_init(void)
{	dprintk("gt2440_key_init()\n");
	return platform_driver_register(&gt2440_key_platform_driver);
}

static void __exit gt2440_key_exit(void)
{	dprintk("gt2440_key_exit()\n");
	platform_driver_unregister(&gt2440_key_platform_driver);
}

module_init(gt2440_key_init);
module_exit(gt2440_key_exit);

MODULE_DESCRIPTION("GT2440 Key board Device Driver");
MODULE_AUTHOR("Liguang13579<1659890447@qq.com>");
MODULE_LICENSE("GPL v2");
